package com.jse.jakarta;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.script.CompiledScript;

import com.jse.Fs;
import com.jse.IP;
import com.jse.Io;
import com.jse.Ioc;
import com.jse.Js;
import com.jse.Jse;
import com.jse.Lang;
import com.jse.Log;
import com.jse.Refs;
import com.jse.Strings;
import com.jse.Three;
import com.jse.Tuple;
import com.jse.api.Oss;
import com.jse.jdbc.Jdbc;
import com.jse.json.JsonArray;
import com.jse.json.JsonObject;
import com.jse.json.XML;

import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;

@SuppressWarnings("unchecked")
public class Web implements jakarta.servlet.ServletContainerInitializer {

	public static ServletContext sc;
	public static RequestDispatcher staticDispatcher;//默认的static转发器
	
	public final static Log log=Log.get("Web");
//	public final static Map<String,Object> DATAS=new ConcurrentHashMap<>();//data(n,v)只存储通用
	public final static Map<String,Object> SESSIONS=new ConcurrentHashMap<>();
	public final static Set<String> STATIC_PRIFIX=Lang.ofSet("/favicon.ico","/upload/","/assets/","/image/","/img/","/js/","/css/",
			"/font/","/fonts/","/public/","/static/","/404.html","/403.html","/500.html");
	public final static Set<String> STATIC_SUFFIX=Lang.ofSet("ico","css","js","htm","txt","mjs","xml",
			"jpg","png","jpeg","gif","webp","bmp","svg","swf","map","crdownload",
			"aac","avi","mp4","mp3","mdi","csv","doc","docx","xls","xlsx","ppt","pptx","rtf","wav","pdf",
			"otf","tif","ttf","eot","tiff","woff","woff2","7z","rar","tar","zip"
			);//后缀可定制取消
	//多线程写也是相同的 更多在读
	public final static Map<String,Three<Path,Long,Tuple<CompiledScript,JsonObject>>> FILTER_FUNS=new HashMap<>();//过滤器
	public final static Map<String,Three<Path,Long,Tuple<CompiledScript,JsonObject>>> FIXED_FUNS=new HashMap<>();//固定
	public final static Map<String,Three<Path,Long,Tuple<CompiledScript,JsonObject>>> REGEX_FUNS=new HashMap<>();//正则

	//scoped未正式前先使用threadLocal
	final static ScopedValue<Tuple<HttpServletRequest,HttpServletResponse>> RQRS =  ScopedValue.newInstance();
	public static int existsNot=-1;
	
	
	@Override
	public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
		sc=ctx;
	    if (ctx.getClass().getSimpleName().equals("NoPluggabilityServletContext")) {
	    	  sc = (ServletContext)Refs.getPrivateField(ctx,"sc",ServletContext.class);
	    }
	    Jse.tempDir=(File)ctx.getAttribute("jakarta.servlet.context.tempdir");
	    if(Jse.tempDir==null)Jse.tempDir=new File(System.getProperty("java.io.tmpdir"));
		var d=ctx.addServlet("jse",new JseServlet());
		d.setAsyncSupported(true);//异步
		d.setMultipartConfig(new MultipartConfigElement(Jse.tempDir.toString(),
				104857600l,//100m
				524288000l,//500m
				0));
		d.addMapping("/");
		ctx.addListener(new JseWebListener());//自定义server暂不支持
		log.info("ServletContainerInitializer init!");
	}
	
	public static void servletInit(ServletConfig conf) throws Exception {
		if(sc==null)sc=conf.getServletContext();//可能存在未通过该类初始从servlet重新获取
		if(Jse.tempDir==null)Jse.tempDir=(File)sc.getAttribute(ServletContext.TEMPDIR);
		if(Jse.tempDir==null)Jse.tempDir=new File(System.getProperty("java.io.tmpdir"));
		
		if(Jse.conf.containsKey("jse.session.timeout"))sc.setSessionTimeout(Jse.conf.getInt("jse.session.timeout"));//设置超时
		if(Jse.webapp()==null) {
			if(Files.exists(Path.of(Jse.dir,"web"))) {
				Jse.setWebapp(Jse.dir+"/web");
				Jse.setWebapps(Jse.dir);
			}
		}
		if(Jse.webapp()==null) {//可能存在未设置webapp等，自动获取下
			Jse.setWebapp(sc.getRealPath("/").replace('\\','/'));
			if(Jse.webapp().endsWith("/"))Jse.setWebapp(Jse.webapp().substring(0,Jse.webapp().length()-1));
			Jse.setWebapps(Jse.webapp().substring(0,Jse.webapp().lastIndexOf('/')));
			if(Jse.webapp().contains("tomcat"))Jse.setWebapps(Jse.webapps()+"/webapps");//使用tomcat时的默认webapps
		}
		staticDispatcher=conf.getServletContext().getNamedDispatcher("default");
		sc.setAttribute("root",sc.getContextPath()+"/");//设置默认root变量 如页面中 #(root)
		Jdbc.start();
		Js.execute("var Web=Java.type('com.jse.jakarta.Web');");
		Ioc.BEANS.forEach((name,bean)->{
			if(name.indexOf('.')==-1)Js.set(name, bean);
		});
		var JPATH=Path.of(Jse.jspath());
		Files.walkFileTree(JPATH,EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
				new SimpleFileVisitor<Path>() {
					@Override
					public FileVisitResult visitFile(Path p, BasicFileAttributes attrs) throws IOException {
						String f=p.toString().substring(Jse.jspath().length()).replace('\\','/');
						String suffix=Fs.suffix(f);//后缀
						String mapping=f.substring(0,f.length()-suffix.length()-1);
						try {
							if(mapping.startsWith("/$/")&&"js".equals(suffix)) {//过滤器
								String mapping1=mapping.equals("/$/$")?"":mapping.substring(2);
								FILTER_FUNS.put(mapping1+"/",new Three<Path,Long,Tuple<CompiledScript,JsonObject>>
								(p,Fs.lastModifiedTime(p),Js.compile(p)));
							}
//							else if(mapping.endsWith("id")&&"js".equals(suffix)){//包含正则映射
//								var jsc=Js.js.compile(Files.readString(p));
//								REGEX_FUNS.put(mapping.substring(0,mapping.length()-2)+"{id:\\d+}",
//									new Three<Path,Long, CompiledScript>(p,Files.getLastModifiedTime(p).toMillis(), jsc));
//							}else if(mapping.endsWith("name")&&"js".equals(suffix)){//包含正则映射
//								var jsc=Js.js.compile(Files.readString(p));
//								REGEX_FUNS.put(mapping.substring(0,mapping.length()-2)+"{name}",
//									new Three<Path,Long, CompiledScript>(p,Files.getLastModifiedTime(p).toMillis(), jsc));
//							}else if("js".equals(suffix)){//固定映射
//								var jsc=Js.js.compile(Files.readString(p));
//								FIXED_FUNS.put(mapping,
//									new Three<Path,Long, CompiledScript>(p,Files.getLastModifiedTime(p).toMillis(), jsc));
//							}
						} catch (Exception e2) {
							throw new RuntimeException("编译出错:"+f);
						}
						return super.visitFile(p, attrs);
					}
		});
		if(Fs.exists(Jse.jspath()+"/jse.js"))
		Js.execute(Fs.read(Jse.jspath()+"/jse.js"));
		System.out.println("classpath:"+Jse.classpath);
		System.out.println("jspath:"+Jse.jspath());
		System.out.println("webapps:"+Jse.webapps());
		System.out.println("webapp:"+Jse.webapp());
	}
	
	
	public static String jspath(){return Jse.jspath(RQRS.get().a().getServerName());}
	public static String webapps(){return Jse.webapp();}
	public static String webapp(){return Jse.webapp(RQRS.get().a().getServerName());}
	
	public static Object data(String n) {return sc.getAttribute(n);}
	public static void data(String n,Object v) {sc.setAttribute(n,v);}
	public static Object attr(String n) {return RQRS.get().a().getAttribute(n);}
	public static void attr(String n,Object v) {RQRS.get().a().setAttribute(n, v);}
	public static Object sattr(String n) {return RQRS.get().a().getSession().getAttribute(n);}
	public static void sattr(String n,Object v) {RQRS.get().a().getSession().setAttribute(n, v);}
	
	public static boolean filter(HttpServletRequest req,HttpServletResponse resp,String url,boolean before) {
		var filtermapping=Lang.startsWithGet(url,Web.FILTER_FUNS.keySet());
		if(filtermapping!=null) {//拦截器
			var t=Web.FILTER_FUNS.get(filtermapping);
			if(before) {//是前置方法
				var name=t.c().b().containsKey("before")?"before":"main";
				var r=Js.cFun(t.a(),name,req,resp);
				if(r!=null&&r!=Boolean.TRUE)
				try {View.filter.render(req,resp,r);return false;} catch (Exception e){throw new RuntimeException(e);}
			}else {
				if(t.c().b().containsKey("after")) {//是后置方法并且存在 注册时没有则不会执行除非手动添加
					var r=Js.cFun(t.a(),"after",req,resp);
					if(r!=null&&r!=Boolean.TRUE)
					try {View.filter.render(req,resp,r);return false;} catch (Exception e){throw new RuntimeException(e);}
				}
			}
		}
		return true;
	}

	public static String mapping(String url,String suffix,Map<String,Object> tbl) {//TODO 暂只支持最后一段path
		String s=url;if(s.endsWith("/"))s+="index";
		s=s.endsWith("."+suffix)?s.substring(0,s.length()-(suffix.length()+1)):s;
		int index=s.lastIndexOf('/');
		var s1=s.substring(index+1);
		if(Strings.isNumber(s1)){
			tbl.put("id",Integer.valueOf(s1));
			return s.substring(0,index)+"/id";
		}
		if(Strings.hasUpperCase(s1)||Strings.hasChinese(s1)){//包含大写或中文
			tbl.put("name",s1);
			return s.substring(0,index)+"/name";
		}
        return s;
    }
	
	public static HashMap<String,Object> datas(HttpServletRequest req) {
		HashMap<String,Object> data=new HashMap<>();
		sc.getAttributeNames().asIterator().forEachRemaining(n->{
			data.put(n,sc.getAttribute(n));
		});
		var session=new HashMap<>();
		req.getSession().getAttributeNames().asIterator().forEachRemaining(n->{
			session.put(n,req.getSession().getAttribute(n));
		});
		data.put("session",session);
		req.getAttributeNames().asIterator().forEachRemaining(n->{
			if(n.charAt(0)!='$'&&n.charAt(0)!='@')
			data.put(n,req.getAttribute(n));
		});
		data.put("req",req);
		return data;
	}

	public static JsonObject tbl(HttpServletRequest req,HttpServletResponse resp,String url) throws IOException {
		JsonObject tbl=new JsonObject();
		if("POST".equals(req.getMethod())) {
			if(req.getHeader("Content-type") == null||req.getHeader("Content-type").startsWith("application/x-www-form-urlencoded")){
			 req.getParameterMap().forEach((k,vs)->{if(vs.length==1){tbl.put(k,vs[0]);}else{tbl.put(k,Arrays.asList(vs));}});
					}else if(req.getHeader("Content-type").startsWith("multipart/form-data")) {
						try {
							var d=LocalDateTime.now();
							Map<String,String> ups=new HashMap<>();
							for(Part part:req.getParts()) {
								if(part.getSubmittedFileName()!= null&&part.getSize()>0) {
									tbl.put(part.getName(),part);
									if(url.equals("/jse/up")||Jse.webuploadauto) {
										ups.put(part.getName(),null);
									}
								}else {
									if(part.getName().charAt(0)=='_'&&part.getName().endsWith("_")) {
										ups.put(part.getName(),req.getParameter(part.getName()));
									}else {
										tbl.put(part.getName(),req.getParameter(part.getName()));
									}
								}
							}
							for (String key : ups.keySet()) {
								if(key.charAt(0)!='_'&&!key.endsWith("_")) {//非_开头和结尾
									Part part=req.getPart(key);
									String filePath=ups.get(key);
									if(ups.containsKey("_"+key+"_")) {
										filePath=ups.get("_"+key+"_");
									}else if(ups.containsKey("_f_")) {
										filePath=ups.get("_f_");
									}else if(ups.containsKey("_path_")) {
										filePath=ups.get("_path_")+part.getSubmittedFileName();
									}else {
										String suffix0=Strings.def(Fs.suffix(part.getSubmittedFileName()),"txt");
										filePath="/upload/"+Jse.name+"/"+
									d.getYear()+"/"+d.getMonth()+"/"+d.getDayOfMonth()+"/"+System.nanoTime()+"."+suffix0;//自动路径
									}
									tbl.put(key,filePath);
									String uploadpath=Jse.conf.get("web.upload",Jse.webapps());
									String abspath=uploadpath+filePath;
									Fs.mkdirs(abspath);
									part.write(abspath);//把文件写到指定路径
									if(Jse.conf.containsKey("ali.oss.bucket")) {
										if(filePath.charAt(0)=='/')filePath=filePath.substring(1);
										String oss=Oss.upload(Map.of("path",filePath,"file",abspath));
										System.out.println("oss up file "+oss);
										Jse.conf.add("tempfile",abspath);
										tbl.put(key,oss);
										Fs.delete(Path.of(abspath));
									}
									part.delete();//删除文件项的基础存储，包括删除任何关联的临时磁盘文件。
								}
							}
						} catch (IOException|ServletException e) {e.printStackTrace();
						}
					}else if(req.getHeader("Content-type").startsWith("application/json")) {
							String body=Io.read(req.getInputStream());
							req.setAttribute("$body",body);
							if(body.charAt(0)=='['){
								tbl.put("list",new JsonArray(body));
							}else {
								tbl.putAll(new JsonObject(body));
							}
					}else if(req.getHeader("Content-type").startsWith("application/xml")||
							req.getHeader("Content-type").startsWith("text/xml")) {
						String body=Io.read(req.getInputStream());
						req.setAttribute("$body",body);
						tbl.putAll(XML.toJSONObject(body));
				}else{//text/plain application/javascript
						String body=Io.read(req.getInputStream());
						req.setAttribute("$body",body);
					}
				}else {
					req.getParameterMap().forEach((k,vs)->{if(vs.length==1){tbl.put(k,vs[0]);}else{tbl.put(k,Arrays.asList(vs));}});
		}
		return tbl;
	}

	public static Optional<Object> getHandler(final String uri) {//TODO 获取handler
        return Optional.ofNullable(REGEX_FUNS.keySet().stream()
                .filter(handler -> handler.equals(uri))
                .findFirst());
    }

	public static void cors(HttpServletRequest req, HttpServletResponse resp) {
		if(Jse.allowcors) {
			resp.setHeader("Access-Control-Allow-Origin",Jse.access_control_allow_origin);
			resp.setHeader("Access-Control-Allow-Methods",Jse.access_control_allow_methods);
			resp.setHeader("Access-Control-Max-Age",Jse.access_control_max_age);
			resp.setHeader("Access-Control-Allow-Headers",Jse.access_control_allow_headers);
			resp.setHeader("Access-Control-Allow-Credentials",Jse.access_control_allow_credentials);
		}
	}
	
	public static boolean ipCheck(HttpServletRequest req,HttpServletResponse resp) {
		if(Strings.isNotEmpty(Jse.ipCheck)&&!IP.checkIP(req.getRemoteAddr(),Jse.ipCheck)) {
			try {
				View.text.render(req, resp, "当前IP不被允许");
			} catch (ServletException | IOException e) {
				e.printStackTrace();
			}
			return false;
	      }
		return true;
	}
	public static String ip() {return RQRS.get().a().getRemoteAddr();}
	
	public static String getDomain() {return getDomain(RQRS.get().a());}

    public static String getDomain(HttpServletRequest request) {
        String domain = request.getHeader("origin");
        if (domain==null||domain.isBlank()) {
            domain = request.getHeader("Referer");
        }
        return domain;
    }
    
    public static boolean existsNot() {
		if(existsNot==-1) {
			if(Fs.exists(Jse.webapp()+"/404.html")) {
				existsNot=1;
			}else {
				existsNot=0;
			}
		}
		return existsNot==1;
	}
}
